﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Caching;
using System.Xml.Linq;
using WebHelper.Helpers;

namespace WebHelper.Modules
{
    [Serializable]
    public class ScheduleModule : ConfigurationSection, IHttpModule
    {
        private static string dummyUrl = "";
        private static List<Job> jobs = new List<Job>(); 

        public static string Register(bool writeToWebConfig, string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement httpModules = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpModules");
            var configElement = httpModules.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ScheduleModule).FullName));
            if (configElement != null)
            {
                writeToWebConfig = false;
            }


            XElement modules = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "modules");
            configElement = modules.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(ScheduleModule).FullName));
            if (configElement != null)
            {
                writeToWebConfig = false;
            }



            //configSection
            if (document.Element("configuration").Element("configSections") == null)
            {
                document.Element("configuration").AddFirst(new XElement("configSections"));
            }
            configElement = document.Element("configuration").Element("configSections").Elements("section").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(ScheduleModule).FullName));
            if (configElement == null)
            {
                document.Element("configuration").Element("configSections").Add(new XElement("section",
                                                                                    new XAttribute("name", typeof(ScheduleModule).Name),
                                                                                    new XAttribute("type", typeof(ScheduleModule).FullName + "," + typeof(ScheduleModule).Assembly.FullName)));
                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "registered the configsections", typeof(ScheduleModule).Name + "  ");
            }

            //module section
            XElement SM = XmlHelper.CreateIfNotExist(document.Root, typeof(ScheduleModule).Name);
            SM.SetAttributeValue("checkPeriod", "1");
            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "registered the module section", typeof(ScheduleModule).Name + "  ");


            //iis6
            configElement = httpModules.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(ScheduleModule).FullName));
            if (configElement == null)
            {
                if (httpModules.Elements("remove").Count() > 0)
                {
                    httpModules.Elements("remove").Last().AddAfterSelf(new XElement("remove", new XAttribute("name", typeof(ScheduleModule).FullName)));
                }
                else
                {
                    httpModules.AddFirst(new XElement("remove", new XAttribute("name", typeof(ScheduleModule).FullName)));
                }
            }
            XElement element = new XElement("add",
                new XAttribute("name", typeof(ScheduleModule).FullName),
                new XAttribute("type", typeof(ScheduleModule).FullName + "," + typeof(ScheduleModule).Assembly.FullName));
            httpModules.Add(element);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "registered in system.web", typeof(ScheduleModule).Name + "  ");

            //iis 7
            configElement = modules.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(ScheduleModule).FullName));
            if (configElement == null)
            {
                if (modules.Elements("remove").Count() > 0)
                {
                    modules.Elements("remove").Last().AddAfterSelf(new XElement("remove", new XAttribute("name", typeof(ScheduleModule).FullName)));
                }
                else
                {
                    modules.AddFirst(new XElement("remove", new XAttribute("name", typeof(ScheduleModule).FullName)));
                }
            }
            element = new XElement("add",
                new XAttribute("name", typeof(ScheduleModule).FullName),
                new XAttribute("type", typeof(ScheduleModule).FullName + "," + typeof(ScheduleModule).Assembly.FullName),
                new XAttribute("preCondition", "integratedMode"));
            modules.Add(element);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "registered in system.webServer", typeof(ScheduleModule).Name + "  ");

            if (writeToWebConfig)
            {
                document.Save(filePath);

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "registered and written at " + filePath, typeof(ScheduleModule).Name + "  ");
            }
            return element.ToString();
        }
        public static void UnRegister(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement configElement = null;
            XElement httpModules = XmlHelper.GetElementsOfPath(document.Root, "system.web", "httpModules").FirstOrDefault();
            if (httpModules != null)
            {
                configElement = httpModules.Elements("remove").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(ScheduleModule).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
                configElement = httpModules.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ScheduleModule).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            XElement modules = XmlHelper.GetElementsOfPath(document.Root, "system.webServer", "modules").FirstOrDefault();
            if (modules != null)
            {
                configElement = modules.Elements("remove").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(ScheduleModule).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
                configElement = modules.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ScheduleModule).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            XElement ConfigSections = XmlHelper.GetElementsOfPath(document.Root, "configSections", "section").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ScheduleModule).FullName));
            if (ConfigSections != null)
            {
                ConfigSections.Remove();
                XElement ScheduleModuleSection = XmlHelper.GetElementsOfPath(document.Root, ConfigSections.Attribute("name").Value).FirstOrDefault();
                if (ScheduleModuleSection != null)
                {
                    ScheduleModuleSection.Remove();
                }
            }
            document.Save(filePath);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "unRegister from web.Congig", typeof(ScheduleModule).Name + "  ");
        }

        public static Job AddJob(string title, DateTime startDate, DateTime endDate, TimeSpan repeatDuration, long repeatNumber)
        {
            Job tempJob = new Job
             {
                 Title = title,
                 StartDate = startDate,
                 EndDate = endDate,
                 RepeatDuration = repeatDuration,
                 RepeatNumbers = repeatNumber,
             };
            ScheduleModule.jobs.Add(tempJob);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "add new Job"
                    + " ,Title=" + tempJob.Title
                    + " ,StartDate=" + tempJob.StartDate
                    + " ,EndDate=" + tempJob.EndDate
                    + " ,RepeatDuration=" + tempJob.RepeatDuration
                    + " ,RepeatNumbers=" + tempJob.RepeatNumbers
                , typeof(ScheduleModule).Name + "  ");

            return tempJob;
        }


        public static bool RemoveJob(string title)
        {
            var item=ScheduleModule.jobs.FirstOrDefault(i=>i.Title==title);
            if(item!=null)
            {
                ScheduleModule.jobs.Remove(item);

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Remove Job"
                        + " ,Title=" + item.Title
                    , typeof(ScheduleModule).Name + "  ");

            }
            return false;
        }


        [ConfigurationProperty("checkPeriod",IsRequired=true)]
        public int CheckPeriod
        {
            get { return (int)this["checkPeriod"]; }
            set { this["checkPeriod"] = value; }
        }



        public void Init(HttpApplication application)
        {
            ScheduleModule custSection = (ScheduleModule)ConfigurationManager.GetSection(typeof(ScheduleModule).Name);
            this.CheckPeriod = custSection.CheckPeriod;

            application.BeginRequest += application_BeginRequest;

            //trace
            try
            {
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Init", typeof(ScheduleModule).Name + "  ");
            }
            catch
            {

            }

        }
        public void Dispose()
        {
            //clean-up code here.

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Dispose", typeof(ScheduleModule).Name + "  ");
        }

        private void application_BeginRequest(object sender, EventArgs e)
        {
            ScheduleModule.dummyUrl = HttpContext.Current.Request.Url.Scheme + "://" + (HttpContext.Current.Request.Url.Authority + (HttpContext.Current.Request.ApplicationPath == "/" ? "" : HttpContext.Current.Request.ApplicationPath))
            + "/dummypage.htm";


            //trace
            WebPageTraceListener wpListener = new WebPageTraceListener();
            if (Trace.Listeners.OfType<WebPageTraceListener>().Count() == 0)
            {
                Trace.Listeners.Add(wpListener);
            }


            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "application_BeginRequest ,checkPeriod=" + this.CheckPeriod, typeof(ScheduleModule).Name + "  ");

            // Prevent duplicate key addition
            if (HttpContext.Current.Cache[typeof(ScheduleModule).Name] == null)
            {
                //trace 
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "add item to cash " + DateTime.Now.ToString() + this.CheckPeriod, typeof(ScheduleModule).Name + "  ");

                HttpContext.Current.Cache.Add(typeof(ScheduleModule).Name, typeof(ScheduleModule).Name, null, DateTime.MaxValue, TimeSpan.FromMinutes(this.CheckPeriod), CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallback));
            }

            //respons
            if (HttpContext.Current.Request.Url.ToString() == ScheduleModule.dummyUrl)
            {
                //trace 
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Response dummypage " + DateTime.Now.ToString() + this.CheckPeriod, typeof(ScheduleModule).Name + "  ");

                HttpContext.Current.Response.Write("dummypage");
                HttpContext.Current.Response.End();
            }
        }


        /// <summary>
        /// Callback method which gets invoked whenever the cache entry expires.
        /// We can do our "service" works here.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="reason"></param>
        public void CacheItemRemovedCallback(string key,object value,CacheItemRemovedReason reason)
        {
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Cache item callback: " + DateTime.Now.ToString(), typeof(ScheduleModule).Name + "  ");

            // execute the jobs
            for (int i = 0; i < ScheduleModule.jobs.Count; i++)
            {
                ScheduleModule.jobs[i].Start();
            }

            // We need to register another cache item which will expire again in one
            // minute. However, as this callback occurs without any HttpContext, we do not
            // have access to HttpContext and thus cannot access the Cache object. The
            // only way we can access HttpContext is when a request is being processed which
            // means a webpage is hit. So, we need to simulate a web page hit and then 
            // add the cache item.
            HitPage();
        }

        /// <summary>
        /// Hits a local webpage in order to add another expiring item in cache
        /// </summary>
        private void HitPage()
        {
            WebClient client = new WebClient();
            client.DownloadData(ScheduleModule.dummyUrl);
        }


        [Serializable]
        public class Job
        {
            private long cycleNo=0;
            private bool f_InProgress=false;
            private DateTime F_LastRunTime = DateTime.MinValue;

            public string Title
            {
                get;
                set;
            }
            public DateTime StartDate
            {
                get;
                set;
            }
            public DateTime EndDate
            {
                get;
                set;
            }
            public TimeSpan RepeatDuration
            {
                get;
                set;
            }
            /// <summary>
            /// 0 means unlimited repeat
            /// </summary>
            public long RepeatNumbers
            {
                get;
                set;
            }
            public bool InProgress
            {
                get
                {
                    return f_InProgress;
                }
            }
            public event EventHandler  Execute;
            public DateTime LastRunTime
            {
                get
                {
                    return F_LastRunTime;
                }
            }
            

            /// <summary>
            /// excute all event handelers associated with excute event if all proper conditions is met . the return value indicate if the job is executed
            /// </summary>
            /// <returns></returns>
            public bool Start()
            {
                bool executed=false;
                //check it is not executing
                DateTime currentDate=DateTime.Now;
                if(!this.InProgress)
                {
                    this.f_InProgress=true;
                    //checked the start and end date and last Duration Date + period
                    if(this.StartDate <= currentDate && this.F_LastRunTime.Add(RepeatDuration) <=currentDate  && this.EndDate> currentDate)
                    {
                        this.cycleNo++;
                        //check the current cycle
                        if(this.RepeatNumbers==0 || this.cycleNo <= RepeatNumbers)
                        {
                            if(this.Execute!=null)
                            {
                                this.Execute(this,EventArgs.Empty);
                                executed=true;
                            }
                        }
                    }
                    this.f_InProgress=false;
                }
                this.F_LastRunTime = this.F_LastRunTime.Add(RepeatDuration);

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") 
                    + " ,Job="+this.Title
                    + " ,currentDate=" + currentDate
                    + " ,StartDate=" + this.StartDate
                    + " ,EndDate=" + this.EndDate
                    + " ,F_LastRunTime=" + this.F_LastRunTime
                    + " ,RepeatDuration=" + this.RepeatDuration
                    + " ,RepeatNumbers=" + this.RepeatNumbers
                    + " ,cycleNo=" + this.cycleNo
                    + " ,Executed="+executed
                    , typeof(ScheduleModule).Name + "  ");

                return executed;
            }
        }
    }
}